package ord2.crawler;

import java.io.File;
import java.net.URL;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

/**
 * Represents a abstract crawler that is gonna run in background.
 *
 * @author José Ivan
 * @author Edviges
 */
public abstract class AbstractCrawler implements Crawler {

    //Lock da variável running
    private final Object lock = new Object();
    //Lock de notificação de start/stop
    private final Object notificationLock = new Object();
    //Lock de notificação de start/stop da URL
    private final Object notificationLockURL = new Object();
    //Tempo de espera para reprocessar as URLs
    private int tempo;
    
    private boolean running = false;
    private LinkedList<CrawlerListener> listeners = new LinkedList<CrawlerListener>();
    private List<URL> urls = Collections.synchronizedList(new LinkedList<URL>());

    public AbstractCrawler(int interval) {

        this.tempo = interval;
    }
    
    @Override
    public final void addURL(URL url) {
        
        this.urls.add(url);
    }

    @Override
    public final void removeURL(URL url) {
        
        this.urls.remove(url);
    }

    @Override
    public void addListener(CrawlerListener cl) {

        //TODO mutex
        this.listeners.add(cl);
    }

    @Override
    public final boolean start() throws Throwable {
        
        if (!isRunning()) {
            
            final AbstractCrawler crawler = this;
            this.running = true;
            (new Thread() {
                
                @Override
                public void run() {
                    
                    crawler.notifyStartStop(CrawlerConstants.START);

                    while (crawler.isRunning()) {
                        
                        for (URL url : crawler.urls) {

                            try {

                                crawler.notifyStartStopURL(CrawlerConstants.START, url);
                                crawler.processURL(url);
                            } catch (Throwable t) {

                                t.printStackTrace();
                            } finally {

                                crawler.notifyStartStopURL(CrawlerConstants.STOP, url);
                            }
                        }

                        try {
                            
                             Thread.sleep(crawler.tempo * 1000);
                        } catch (Exception e) {}
                    }
                    crawler.notifyStartStop(CrawlerConstants.STOP);
                }
            }).start();
            synchronized (this.lock) {
                
                this.running = true;
            }
        }
        
        return false;
    }

    @Override
    public final void stop() throws Throwable {
        
        synchronized (lock) {

            this.running = false;
        }
    }

    @Override
    public boolean isRunning() {
        
        synchronized (lock) {
            
            return running;
        }
    }
    
    /**
     * Class that extends abstract class should implements this method to receive
     * requests. Each request is a URL that should be processed.
     * @param url URL to be processed
     * @throws Throwable if any error occurs
     */
    protected abstract void processURL(URL url) throws Throwable;

    protected void notifyDocument(File f) {

        for (CrawlerListener cl : this.listeners) {

            cl.documentAction(this, f);
        }
    }

    private void notifyStartStop(final int op) {
        
        synchronized (this.notificationLock) {
            
            final AbstractCrawler crawler = this;
            (new Thread() {
            
                @Override
                public void run() {
                
                    synchronized (crawler.notificationLock) {
                        
                        for (CrawlerListener cl : crawler.listeners) {

                            cl.operationAction(op, crawler);
                        }
                        
                        crawler.notificationLock.notify();
                    }
                    
                }
            }).start();
            try {
                
                this.notificationLock.wait();
            } catch (InterruptedException ie) {}
        }
    }
    
    private void notifyStartStopURL(final int op, final URL url) {
        
        synchronized (this.notificationLockURL) {
            
            final AbstractCrawler crawler = this;
            (new Thread() {
            
                @Override
                public void run() {
                
                    synchronized (crawler.notificationLockURL) {
                        
                        for (CrawlerListener cl : crawler.listeners) {

                            cl.urlAction(op, crawler, url);
                        }
                        
                        crawler.notificationLockURL.notify();
                    }
                    
                }
            }).start();
            try {
                
                this.notificationLockURL.wait();
            } catch (InterruptedException ie) {}
        }
    }
}
